package com.zusmart.base.scanner.support;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.zusmart.base.scanner.Scanner;
import com.zusmart.base.scanner.ScannerProcessor;
import com.zusmart.base.scanner.ScannerResult;
import com.zusmart.base.toolkit.AntPathMatcher;
import com.zusmart.base.util.StringUtils;

public abstract class AbstractScanner implements Scanner {

	protected static final AntPathMatcher packagesPathMatcher = new AntPathMatcher(".");
	protected static final AntPathMatcher resourcePathMatcher = new AntPathMatcher("/");

	protected static final String EXT_CLASS = ".class";
	protected static final String EXT_JARFI = ".jar";
	protected static final Map<String, Class<?>> cache = new HashMap<String, Class<?>>();

	private Set<String> patterns = new LinkedHashSet<String>();
	private Set<ScannerProcessor> processors = new LinkedHashSet<ScannerProcessor>();

	@Override
	public void addPattern(String pattern) {
		if (StringUtils.isBlank(pattern)) {
			return;
		}
		this.patterns.add(pattern);
	}

	@Override
	public void addPatterns(Set<String> patterns) {
		if (null == patterns || patterns.isEmpty()) {
			return;
		}
		this.patterns.addAll(patterns);
	}
	
	@Override
	public void addPatterns(String[] patterns) {
		if (null == patterns || patterns.length == 0) {
			return;
		}
		this.patterns.addAll(Arrays.asList(patterns));
	}

	@Override
	public void addProcessor(ScannerProcessor processor) {
		if (null == processor) {
			return;
		}
		this.processors.add(processor);
	}

	@Override
	public Set<ScannerResult> scan(String pattern) {
		this.addPattern(pattern);
		return this.scan();
	}

	@Override
	public Set<ScannerResult> scan(Set<String> patterns) {
		this.addPatterns(patterns);
		return this.scan();
	}

	@Override
	public Set<ScannerResult> scan(String pattern, ScannerProcessor processor) {
		this.addProcessor(processor);
		return this.scan();
	}

	@Override
	public Set<ScannerResult> scan(Set<String> patterns, ScannerProcessor processor) {
		this.addPatterns(patterns);
		this.addProcessor(processor);
		return this.scan();
	}

	@Override
	public Set<ScannerResult> scan() {
		if (this.patterns.isEmpty()) {
			return Collections.emptySet();
		}
		List<String> list = new ArrayList<String>();
		list.addAll(this.patterns);
		Collections.sort(list);
		Set<ScannerResult> scanns = this.doScan(list);
		Set<ScannerResult> result = new LinkedHashSet<ScannerResult>();
		if (null != this.processors && this.processors.size() > 0) {
			for (ScannerResult r : scanns) {
				if (this.processor(r)) {
					result.add(r);
				}
			}
		} else {
			result.addAll(scanns);
		}
		return result;
	}

	protected boolean processor(ScannerResult result) {
		if (this.processors.isEmpty()) {
			return true;
		}
		for (ScannerProcessor processor : this.processors) {
			if (processor.process(result) == false) {
				return false;
			}
		}
		return true;
	}

	protected boolean matchPath(boolean isPackagePath, String path) {
		final AntPathMatcher pathMatcher = isPackagePath ? packagesPathMatcher : resourcePathMatcher;
		for (String pattern : this.patterns) {
			if (pathMatcher.isPattern(pattern)) {
				if (pathMatcher.match(pattern, path)) {
					return true;
				}
			} else {
				if (pattern.equals(path)) {
					return true;
				}
			}
		}
		return false;
	}

	protected Class<?> classForName(String className) {
		if (StringUtils.isBlank(className)) {
			return null;
		}
		if (cache.containsKey(className)) {
			return cache.get(className);
		}
		try {
			Class<?> clazz = Class.forName(className);
			cache.put(clazz.getName(), clazz);
			return clazz;
		} catch (ClassNotFoundException e) {
			return null;
		}
	}

	protected boolean isClassFile(String filePath) {
		return filePath.endsWith(EXT_CLASS);
	}

	protected boolean isJarFile(File file) {
		return file.getName().endsWith(EXT_JARFI);
	}

	protected abstract Set<ScannerResult> doScan(List<String> patterns);

}